/*
 * Copyright (c) 2009-2011, okazoh_tk. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  - Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *  - Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *  - Neither the name of the copyright holder nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "alkes/media/ImageBuffer.h"
#include "alkes/media/ImageFileWriter.h"
#include "alkes/math/Math.h" // alkes_abs
#include <string.h>

namespace alkes {

class ImageBufferWriterDelegate
    : public WriterDelegate
{
public:
    ImageBufferWriterDelegate(void* p, int32_t real_pitch);
    virtual void write(ImageWriter& writer, PixelFormat format, int32_t pitch, int32_t width, int32_t height);

private:
    uint8_t* p_;
    int32_t real_pitch_;
};

ImageBuffer::ImageBuffer(bool write_only)
{
    if (write_only)
        denyRead();
}

ImageBuffer::~ImageBuffer()
{
}

bool ImageBuffer::isReadable() const
{
    return !flag(AL_BUFATTR_DENY_READ);
}

void ImageBuffer::denyRead()
{
    setFlag(AL_BUFATTR_DENY_READ, true);
}

bool ImageBuffer::isWritable() const
{
    return !flag(AL_BUFATTR_DENY_WRITE);
}

void ImageBuffer::denyWrite()
{
    setFlag(AL_BUFATTR_DENY_WRITE, true);
}

void ImageBuffer::getImageSize(uint16_t& width, uint16_t& height) const
{
    getSize(width, height);
}

void* ImageBuffer::lockRect(const Rect* rect, uint32_t lock_mode, int32_t* pitch)
{
    if (isLocked())
        return 0;   // already locked!

    if (0 != (lock_mode & AL_LOCKMODE_READ) && !isReadable())
        return 0;

    if (0 != (lock_mode & AL_LOCKMODE_WRITE) && !isWritable())
        return 0;

    // Accept buffer rect.
    Rect lock_rect(getRect());
    if (rect)
    {
        lock_rect.intersectWith(*rect);
        if (lock_rect != *rect)
        {
            // incorrect rect
            return 0;
        }
    }

    if (lock_rect.isNull())
        return 0;   // invalid rect

    void* ptr = onLockRect(lock_rect, lock_mode, pitch);

    if (ptr)
    {
        locked_rect_ = lock_rect;

        if (lock_mode & AL_LOCKMODE_READ)
            setFlag(AL_BUFATTR_LOCK_FOR_READ);

        if (lock_mode & AL_LOCKMODE_WRITE)
            setFlag(AL_BUFATTR_LOCK_FOR_WRITE);
    }

    return ptr;
}

void ImageBuffer::unlockRect()
{
    if (isLocked())
    {
        onUnlockRect(locked_rect_);

        clearFlag(AL_BUFATTR_LOCK_FOR_READ);
        clearFlag(AL_BUFATTR_LOCK_FOR_WRITE);
    }
}

bool ImageBuffer::isLocked() const
{
    return flag(AL_BUFATTR_LOCK_FOR_READ) || flag(AL_BUFATTR_LOCK_FOR_WRITE);
}

const Rect ImageBuffer::getRect() const
{
    uint16_t w, h;
    getSize(w, h);
    Rect rect = {0,0,w,h};
    return rect;
}

const Rect ImageBuffer::getImageRect() const
{
    uint16_t w, h;
    getImageSize(w, h);
    Rect rect = {0,0,w,h};
    return rect;
}

ImageBufferCaps ImageBuffer::getCaps() const
{
    ImageBufferCaps caps;
    getImageSize(caps.image_width, caps.image_height);
    getSize(caps.buf_width, caps.buf_height);
    caps.format = getPixelFormat();
    return caps;
}

bool ImageBuffer::resizeImageSize(uint16_t new_width, uint16_t new_height)
{
    uint16_t width, height;
    getSize(width, height);

    if (width < new_width || height < new_height)
        return false;

    return onResizeImageSize(new_width, new_height);
}

bool ImageBuffer::onResizeImageSize(uint16_t /*new_width*/, uint16_t /*new_height*/)
{
    return false;
}

bool ImageBuffer::copyFrom(ImageBuffer* src, const Rect* src_rect, uint16_t dst_x, uint16_t dst_y)
{
    AL_PRE_COND(src
        && AL_PIXEL_FORMAT_UNKNOWN != getPixelFormat(),
        false);

    PixelFormat src_format = src->getPixelFormat();
    const PixelFormatTraits* src_traits = alkes_get_pixel_format_traits(src_format);

    AL_PRE_COND(src_traits, false);

    Rect srect(src->getRect());
    if (src_rect)
        srect.intersectWith(*src_rect);

    // empty src rect.
    if (srect.isNull())
        return true;    // do nothing.

    Rect drect = {dst_x, dst_y, srect.width, srect.height};
    drect.intersectWith(getRect());

    // empty dest rect
    if (drect.isNull())
        return true;    // do nothing.

    // adjust size to dst image.
    srect.width = drect.width;
    srect.height = drect.height;

    int32_t spitch;
    uint8_t* sp;

    if (NULL == (sp = reinterpret_cast<uint8_t*>(src->lockRect(&srect, AL_LOCKMODE_READ, &spitch))))
        return false;

    bool ret = copyFrom(sp, spitch, src_traits, drect);

    src->unlockRect();

    return ret;
}

bool ImageBuffer::copyFrom(uint8_t* buffer, int32_t pitch, const PixelFormatTraits* traits, const Rect& dst_rect)
{
    int32_t dpitch;
    uint8_t* dp;
    uint8_t* sp = buffer;
    const PixelFormatTraits* dst_traits = alkes_get_pixel_format_traits(getPixelFormat());

    AL_PRE_COND(buffer && traits && dst_traits, false);
    AL_PRE_COND(traits->bytes * dst_rect.width <= alkes_abs(pitch), false);

    if (NULL == (dp = reinterpret_cast<uint8_t*>(this->lockRect(&dst_rect, AL_LOCKMODE_WRITE, &dpitch))))
        return false;

    if (traits == dst_traits)
    {
        int32_t copy_length = traits->bytes * dst_rect.width;
        uint16_t y;
        for (y = 0; y < dst_rect.height; ++y)
        {
            memcpy(dp, sp, copy_length);
            dp += dpitch;
            sp += pitch;
        }
    }
    else
    {
        uint16_t y, x;
        uint8_t* tmp_sp;
        uint8_t* tmp_dp;
        for (y = 0; y < dst_rect.height; ++y)
        {
            for (
                x = 0, tmp_sp = sp, tmp_dp = dp;
                x < dst_rect.width;
                ++x, tmp_sp += traits->bytes, tmp_dp += dst_traits->bytes)
            {
                dst_traits->setPixel(tmp_dp, traits->getPixel(tmp_sp));
            }
            sp += pitch;
            dp += dpitch;
        }
    }

    this->unlockRect();

    return true;
}

bool ImageBuffer::save(const char* filepath, const Rect* rect)
{
    ImageFileWriter writer(filepath);

    return save(writer, rect);
}

bool ImageBuffer::save(ImageWriter& writer, const Rect* rect)
{
    int32_t pitch;
    void* p = lockRect(rect, AL_LOCKMODE_READ, &pitch);
    if (!p)
        return false;

    PixelFormat format = getPixelFormat();

    bool ret;

    // whole buffer
    if (!rect)
    {
        uint16_t width, height;

        // Use a size of image.
        getImageSize(width, height);

        // Save whole buffer.
        ret = writer.write(format, p, pitch, width, height);
    }
    else
    {
        ImageBufferWriterDelegate deleg(p, pitch);

        ret = writer.writeDelegate(format, alkes_get_byteperpixel(format) * rect->width, rect->width, rect->height, &deleg);
    }

    unlockRect();

    return ret;
}

ImageBufferWriterDelegate::ImageBufferWriterDelegate(void* p, int32_t real_pitch)
: p_((uint8_t*)p)
, real_pitch_(real_pitch)
{
}

void ImageBufferWriterDelegate::write(ImageWriter& writer, PixelFormat format, int32_t /*pitch*/, int32_t width, int32_t height)
{
    int32_t y;
    uint8_t* p = p_;

    for (y = 0; y < height; ++y, p += real_pitch_)
    {
        invokeWriteScanline(writer, format, p, real_pitch_, width, y);
    }
}

}
